﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

using ABCat.Shared.Properties;

namespace ABCat.Core.Plugins
{
	public class PluginsFactory
	{
		#region + Events +

		public event EventHandler LiveInstancesChanged;

		#endregion
		#region + Fields +

		private readonly Context _context;
		private readonly List<IPlugin> _liveInstances = new List<IPlugin>();
		private readonly HashSet<Type> _pluginConfigTypes = new HashSet<Type>();
		private readonly List<PluginCreatorAttribute> _pluginsCreators = new List<PluginCreatorAttribute>();
		private readonly string _pluginsFolder;

		#endregion
		#region + Properties +

		public IEnumerable<IPlugin> LiveInstances
		{
			get
			{
				return _liveInstances;
			}
		}

		public string PluginsFolder
		{
			get { return _pluginsFolder; }
		}

		#endregion
		#region + Ctor +

		public PluginsFactory(Context context, [NotNull] string pluginsFolder)
		{
			_context = context;
			_pluginsFolder = pluginsFolder;

			if (Directory.Exists(PluginsFolder))
			{
				LoadPlugins(PluginsFolder, "ABCat.Plugins.*.dll");
				LoadPlugins(PluginsFolder, "ABCat.Plugins.*.exe");
			}
		}

		#endregion
		#region + Logic +

		public IEnumerable<Type> GetConfigAttributes()
		{
			return _pluginConfigTypes.ToArray();
		}

		public IEnumerable<PluginCreatorAttribute> GetPlugins<T>(Func<Type, bool> predicate = null)
			where T : IPlugin
		{
			return _pluginsCreators.Where(plugin => plugin.PluginType.GetInterfaces().Contains(typeof(T)) && (predicate == null || predicate(plugin.PluginType)));
		}

		private void LoadPlugins(string plugInsFolder, string filePattern)
		{
			var dlls = Directory.GetFiles(plugInsFolder, filePattern, SearchOption.AllDirectories);

			foreach (var dll in dlls.Where(item => !item.ToLower().Contains("vshost.exe")))
			{
				var asm = Assembly.LoadFile(dll);
				foreach (var type in asm.GetTypes())
				{
					foreach (var pluginCreatorAttribute in type.GetCustomAttributes(typeof(PluginCreatorAttribute)).Cast<PluginCreatorAttribute>().Where(item => item.IsEnabled))
					{
						pluginCreatorAttribute.Init(type);
						Register(pluginCreatorAttribute);
					}
					if (type.InheritsFrom(typeof(Config)))
					{
						_pluginConfigTypes.Add(type);
					}
				}
			}
		}

		private void RaiseLiveInstancesChanged()
		{
			var liveInstancesChanged = LiveInstancesChanged;
			if (liveInstancesChanged != null)
			{
				liveInstancesChanged(this, EventArgs.Empty);
			}
		}

		private void Register(PluginCreatorAttribute pluginCreatorAttribute)
		{
			pluginCreatorAttribute.OnInstanceCreatedAction = (plugin) =>
				{
					lock (_liveInstances)
					{
						_liveInstances.Add(plugin);
					}
					RaiseLiveInstancesChanged();
				};

			pluginCreatorAttribute.OnInstanceDisposedAction = (plugin) =>
				{
					lock (_liveInstances)
					{
						_liveInstances.Remove(plugin);
					}
					RaiseLiveInstancesChanged();
				};

			_pluginsCreators.Add(pluginCreatorAttribute);
		}

		#endregion
	}
}